#ifndef ANIMATOR_H
#define ANIMATOR_H

#include <glm/glm.hpp>
#include <map>
#include <vector>
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
#include "Scene/animation.h"
#include "Scene/bone.h"

namespace CC
{

#define MAX_BONES (200)

	class Animator
	{
	public:
		// Animator(Animation *animation)
		// {
		// 	m_CurrentTime = 0.0;
		// 	m_CurrentAnimation = animation;
		// 	m_CurrentMixTime = 0.0;
		// 	m_OldAnimation = NULL;

		// 	m_FinalBoneMatrices.reserve(MAX_BONES);

		// 	for (int i = 0; i < MAX_BONES; i++)
		// 		m_FinalBoneMatrices.push_back(glm::mat4(1.0f));
		// }
		Animator()
		{
			m_CurrentTime = 0.0;
			m_MixTotalTime = 0.0;
			m_CurrentMixTime = 0.0;
			m_CurrentAnimation = NULL;
			m_OldAnimation = NULL;

			Init();
		}
		
		~Animator()
		{
			// if (m_CurrentAnimation)
			// 	delete m_CurrentAnimation;
		}

		void Init(){
			// static bool isInit = false;
			// if(isInit) return;

			// isInit = true;

			m_FinalBoneMatrices.reserve(MAX_BONES);

			for (int i = 0; i < MAX_BONES; i++)
				m_FinalBoneMatrices.push_back(glm::mat4(1.0f));
		}

        void SetAnimationInfo(AnimationInfo* info) {
            m_AnimationInfo = info;
        }

		void UpdateAnimation(float dt, bool loop, bool& finish, int& currentFrame)
		{
			m_DeltaTime = dt;
			if (m_CurrentAnimation)
			{
				if (m_OldAnimation)
				{
					m_CurrentMixTime += m_CurrentAnimation->GetTicksPerSecond() * dt;

					if (m_CurrentMixTime >= m_MixTotalTime)
					{
						// end mix action
						m_OldAnimation = NULL;
					}
					else
					{
						// mix action
						CalculateBoneTransform(&m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f));
					}
				}
				else
				{
					m_CurrentTime += m_CurrentAnimation->GetTicksPerSecond() * dt;
                    if (loop)
					{
                        if (m_CurrentAnimation->GetDuration() <= 0) {
                            m_CurrentTime = 0.0;
                        }
                        else {
                            m_CurrentTime = fmod(m_CurrentTime, m_CurrentAnimation->GetDuration());
                            CalculateBoneTransform(&m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f));
                        }
						
					}
					else
					{
						if (m_CurrentTime >= m_CurrentAnimation->GetDuration())
						{
							finish = true;
							m_CurrentTime = m_CurrentAnimation->GetDuration();
						}
						else
						{
							CalculateBoneTransform(&m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f));
						}
					}
                    currentFrame = m_CurrentFrame;
				}
			}
			else
			{
				//            printf("updateAnimation error.\n");
			}
		}

		void PlayAnimation(Animation *pAnimation, float m_mixTime = 0.0)
		{
			m_MixTotalTime = m_mixTime;

			if (m_OldAnimation == NULL && m_MixTotalTime != 0.0)
			{
				m_OldAnimation = m_CurrentAnimation; // A->B
			}
			else
			{
				// A->C | A->B no mix
			}
			m_CurrentAnimation = pAnimation;

			m_CurrentTime = 0.0f;
			m_CurrentMixTime = 0.0f;
		}

		void StopAnimation(){
			m_CurrentAnimation = nullptr;
		}

		void CalculateBoneTransform(const AssimpNodeData *node, glm::mat4 parentTransform)
		{
			if (!m_CurrentAnimation || !node)
				return;

			std::string nodeName = node->name;
			glm::mat4 nodeTransform = node->transformation;

			Bone *bone = m_CurrentAnimation->FindBone(nodeName);
			if (m_OldAnimation)
			{
				Bone *boneOld = m_OldAnimation->FindBone(nodeName);
				if (bone && boneOld)
				{
                    if(0.0 != m_MixTotalTime)
					    boneOld->UpdateMixAction(m_CurrentMixTime / m_MixTotalTime, boneOld, bone);
					nodeTransform = boneOld->GetLocalTransform();
				}
			}
			else
			{
				if (bone)
				{
					bone->Update(m_CurrentTime);
                    m_CurrentFrame = bone->GetCurrentFrame();
					nodeTransform = bone->GetLocalTransform();
				}
			}

			glm::mat4 globalTransformation = parentTransform * nodeTransform;

			auto boneInfoMap = m_CurrentAnimation->GetBoneIDMap();
            std::unordered_map<std::string, BoneInfo>::iterator it = boneInfoMap.find(nodeName);
			if (it != boneInfoMap.end())
			{
				int index = it->second.id;

				glm::mat4 offset = it->second.offset;

                if(index < MAX_BONES)
				    m_FinalBoneMatrices[index] = globalTransformation * offset;
			}
			else
			{
				//            printf("calculateBoneTransform no find nodeName=%s\n", nodeName.c_str());
			}

			for (int i = 0; i < node->childrenCount; i++)
				CalculateBoneTransform(&node->children[i], globalTransformation);
		}

		std::vector<glm::mat4> GetFinalBoneMatrices()
		{
			return m_FinalBoneMatrices;
		}

        glm::mat4 GetFinalBoneMatriceByBoneName(std::string name) {

            if (m_CurrentAnimation) {
                auto boneInfoMap = m_CurrentAnimation->GetBoneIDMap();
                std::unordered_map<std::string, BoneInfo>::iterator it = boneInfoMap.find(name);
                if (it != boneInfoMap.end())
                {
                    int index = it->second.id;

                    if (index < MAX_BONES)
                        return m_FinalBoneMatrices[index];
                }
            }
            
            return glm::mat4(1.0);
        }

	private:
		std::vector<glm::mat4> m_FinalBoneMatrices;
		Animation* m_CurrentAnimation;
		Animation* m_OldAnimation;
		float m_CurrentTime;
		float m_DeltaTime;
		float m_MixTotalTime;
		float m_CurrentMixTime;
        int m_CurrentFrame;
        AnimationInfo * m_AnimationInfo = NULL;
	};
}
#endif